<!-- 基础的crud组件，适用于定制化不高的增删改查页面 -->
<template>
  <avue-crud ref="crudRef" v-model="formData" v-bind="attrs" v-model:page="pageData" v-model:search="searchData"
    :before-open="handleBeforeOpen" :before-close="handleBeforeClose" :class="selectType === 'radio' && 'radio_crud'"
    :data="tableData" :option="initOption" :table-loading="loading" @select="handleSelect"
    @selection-change="handleChangeSelect" @size-change="handleChangeSize" @current-change="handleChangeCurrent"
    @search-change="handleSearch" @search-reset="handleReset" @refresh-change="getList" @row-save="handleAddSave"
    @row-update="handleEditSave" @row-del="handleDel">
    <template v-for="slot in slotList" :key="slot.name" #[slot.name]="data">
      <slot v-bind="data" :formData="formData" :handleChangeStatus="handleChangeStatus" :name="slot.name"
        :searchData="searchData"></slot>
    </template>
    <template #menu="data">
      <el-button v-if="initOption.statusBtn" :size="initOption.size" icon="TurnOff" text type="primary"
        @click="handToggleStatus(data.row)">{{
          data.row.status === 'Disabled' ? '启用' : '停用'
        }}
      </el-button>
      <el-button v-if="initOption.addSonBtn" text type="primary" icon="Plus"
        @click="handleAddSon(data.row)">新增子集</el-button>
      <slot v-bind="data" :formData="formData" :handleChangeStatus="handleChangeStatus" name="menu"></slot>
    </template>
    <template v-if="statusTagProp" #[statusTagProp]="{ column, row }">
      <basic-tag :key="row[statusTagProp]" :column="column" :row="row" :statusMap="statusMap"></basic-tag>
    </template>
  </avue-crud>
</template>

<script setup>
import { computed, nextTick, onMounted, useAttrs, watch, ref } from 'vue'
import { useRoute } from 'vue-router'
import emitter from '@/utils/mitt.js'
import enumData from '@/config/enum.js'
import request from '@/utils/request.js'
import { apiGetEnumByCode } from '@/apis/objectModel/enumData.js'
import { getCurMenuByCurRoute, clearObjAttr } from '@/utils/index.js'

const curRoute = useRoute()
// 获取当前实例
const attrs = useAttrs()
const emits = defineEmits(['selection-change', 'formDataChange', 'select'])

// 定义 props
const props = defineProps({
  // 默认为list， 可以选择为refer
  type: {
    type: String,
    default: 'list'
  },
  // avue组件参数
  option: {
    type: Object,
    default: () => ({}),
  },
  // 业务地址，crud接口会根据此生成
  // 例如 a/b/list
  // 那么businessUrl则为a/b
  businessUrl: String,
  // 如果接口比较特殊，例如是a/b/listHasChildren,则可以单独传接口地址
  apiGetListUrl: String,
  apiGetListMethod: 'get',
  apiAddUrl: String,
  apiEditUrl: String,
  apiDelUrl: String,
  apiReferUrl: String,
  // 默认查询参数
  defaultListParams: {
    type: Object,
    default: () => ({
      orderBy: 'code',
      orderMethod: 'asc',
    }),
  },
  //列表请求的额外参数
  apiListParams: {
    type: Object,
    default: () => ({}),
  },
  // 新增请求的额外参数
  apiAddParams: {
    type: Object,
    default: () => ({}),
  },
  // 编辑请求的额外参数
  apiEditParams: {
    type: Object,
    default: () => ({}),
  },
  // 删除请求的额外参数
  apiDelParams: {
    type: Object,
    default: () => ({}),
  },
  // 请求接口来源
  apiSource: {
    type: [String, Object],
    default: undefined,
  },
  // 选择类型
  // multiple多选
  // radio单选
  // none不选
  selectType: {
    type: String,
    default: 'none',
  },
  // 自动请求列表数据
  autoLoad: {
    type: Boolean,
    default: true,
  },
  // 删除数据的提示信息
  delTip: {
    type: String,
    default: '确认删除该条数据？',
  },
  // 勾选key，用于勾线后回显自动勾选
  selectedKey: {
    type: String,
    default: 'id',
  },
  // 分页配置
  pageData: {
    type: Object,
    default: () => ({}),
  },
  // 参数变化自动刷新
  paramsChangeAutoRefresh: {
    type: Boolean,
    default: true
  },
  // 是否启用按钮权限，如果启动，就按照菜单下的按钮进行权限控制
  // 增删改查启用停用为内置按钮，会自动控制显示隐藏
  // 其余自定义按钮则使用v-btnPermission全局指令进行控制
  btnPermission: {
    type: Boolean,
    default: false
  },
  // 打开之前
  beforeOpen: Function,
  // 生命周期钩子
  beforeGetList: Function, //获取列表之前，可以处理请求参数
  afterGetList: Function, //获取列表之后，可以处理列表数据
  afterGetListSuccess: Function, //列表数据已经展示完之后
  afterGetListError: Function, //请求失败
  beforeAdd: Function,
  afterAdd: Function,
  afterAddSuccess: Function,
  afterAddError: Function,
  beforeEdit: Function,
  afterEdit: Function,
  afterEditSuccess: Function,
  afterEditError: Function,
  beforeDel: Function,
  afterDel: Function,
  afterDelSuccess: Function,
  afterDelError: Function,
})

const setCurrentRowHandler = (row) => {
  nextTick(() => {
    crudRef.value?.toggleSelection([row]);
  })
};

// 默认选中的表格数据
const defaultSelectList = defineModel('defaultSelectList', { default: [] })
const slots = useSlots()
const noIncludesSlot = ['menu']

// 插槽列表
const slotList = computed(() => {
  if (slots) {
    return Object.keys(slots)
      .filter((item) => !noIncludesSlot.includes(item))
      .map((t) => ({ name: t, slot: slots[t] }))
  }
  return []
})

// 自动生成接口数据
const getApiFun = (type) => {
  const apiAdd = (data, loading) => {
    return request({
      url: props.apiAddUrl ? props.apiAddUrl : props.businessUrl + '/add',
      method: 'post',
      data,
      loading
    })
  }
  const apiEdit = (data, loading) => {
    return request({
      url: props.apiEditUrl ? props.apiEditUrl : props.businessUrl + '/edit',
      method: 'put',
      data,
      loading
    })
  }
  const apiDel = (ids) => {
    return request({
      url: (props.apiDelUrl ? props.apiDelUrl : props.businessUrl) + `/${ids}`,
      method: 'delete',
    })
  }
  const apiGetList = (params, loading) => {
    let url = ''
    if (props.type === 'list') {
      url = (props.apiGetListUrl ? props.apiGetListUrl : (props.businessUrl + '/list'))
    } else {
      url = (props.apiReferUrl ? props.apiReferUrl : (props.businessUrl + '/referGrid'))
    }
    if (props.apiGetListMethod == 'post') {
      return request({
        url,
        method: 'post',
        data: params,
        autoUntie: false,
        loading
      })
    } else {
      return request({
        url,
        method: props.apiGetListMethod,
        params,
        autoUntie: false,
        loading
      })
    }
  }
  const apiGetCode = (params, loading) => {
    return request({
      url: props.businessUrl + '/produceBeforeCode',
      method: 'get',
      params,
      loading,
      resField: 'obj'
    })
  }
  const apiChangeStatus = () => {
    return request({
      url: props.businessUrl + '/changeStatus',
      method: 'post',
      data,
      loading,
    })
  }
  const apiEnable = (data) => {
    return request({
      url: props.businessUrl + '/enable',
      method: 'post',
      data,
    })
  }
  const apiDisable = (data) => {
    return request({
      url: props.businessUrl + '/disable',
      method: 'post',
      data,
    })
  }
  if (type === 'add') {
    return apiAdd
  }
  if (type === 'edit') {
    return apiEdit
  }
  if (type === 'del') {
    return apiDel
  }
  if (type === 'list') {
    return apiGetList
  }
  if (type === 'code') {
    return apiGetCode
  }
  if (type === 'changeStatus') {
    return apiChangeStatus
  }
  if (type === 'enable') {
    return apiEnable
  }
  if (type === 'disable') {
    return apiDisable
  }
  return {
    apiAdd,
    apiEdit,
    apiDel,
    apiGetList,
    apiGetCode,
    apiChangeStatus,
    apiEnable,
    apiDisable,
  }
}

// 合并请求方法（有些方法可能不是规定的那些CRUD）
const getApiMap = () => {
  let apiMap = getApiFun()
  const curPath = curRoute.path
  if (typeof props.apiSource === 'object') {
    apiMap = { ...apiMap, ...props.apiSource }
  } else if (typeof props.apiSource === 'string') {
    let modules = import.meta.glob('../../../apis/**/*.js', { eager: true })
    let newApi = modules[`../../../apis${props.apiSource}.js`]
    apiMap = { ...apiMap, ...newApi }
  }
  return apiMap
}

const {
  apiGetList,
  apiAdd,
  apiDel,
  apiEdit,
  apiDisable,
  apiEnable,
  apiGetCode,
  apiChangeStatus,
} = getApiMap()
// 获取code或者禁用
const getOrDisabledCode = async (done, loading) => {
  loading && loading()
  const { afterCode, prefixCode, editFlag } = await apiGetCode()
  initOption.value.column = initOption.value.column.map((item) => {
    if (item.autoCode) {
      if (afterCode === true) {
        item.disabled = true
      } else {
        if (prefixCode) {
          item.value = prefixCode
          if (editFlag === true) {
            item.disabled = false
          } else {
            item.disabled = true
          }
        }
      }
    }
    return item
  })
  done && done()
}
// 默认的option参数
const initOption = ref({
  addBtn: true,
  editBtn: true,
  delBtn: true,
  menuWidth: 240,
  selection: false,
  labelWidth: 140,
  checkStrictly: true,
  column: [],
})
// 状态字段的prop至
const statusTagProp = ref()
const mergeOption = async () => {
  // 融合本地，传入的option数据
  initOption.value = { ...initOption.value, ...props.option }
  initOption.value.selection = props.selectType !== 'none'
  // 对column进行处理
  for (const item of initOption.value.column) {
    if (item.statusTag) {
      statusTagProp.value = item.prop
    }
    // 是否禁用code
    // 发现给value赋值会有延迟性，怀疑是框架bug，带排查，所以在页面初始化的时候提前调用一次
    if (item.autoCode) {
      getOrDisabledCode()
    }
    if (item.enumCode) {
      item.dicData = enumData[item.enumCode]
    }
    if (item.dicCode) {
      item.dicData = await apiGetEnumByCode(item.dicCode)
    }
  }
  // 按钮权限处理
  if (props.btnPermission) {
    const curMenu = getCurMenuByCurRoute()
    const btnList = curMenu?.buttons || []
    initOption.value.addBtn = btnList.some(item => item.code.endsWith('ADDBTN'))
    initOption.value.viewBtn = btnList.some(item => item.code.endsWith('VIEWROW'))
    initOption.value.delBtn = btnList.some(item => item.code.endsWith('EDITROW'))
    initOption.value.editBtn = btnList.some(item => item.code.endsWith('DELETEROW'))
    initOption.value.statusBtn = btnList.some(item => item.code.endsWith('STATUSROW'))//状态，停用/启用
    initOption.value.addSonBtn = btnList.some(item => item.code.endsWith('ADDSONBTN'))//新增子集
  }
}
mergeOption()
// 弹出打开之前对code进行操作
const handleBeforeOpen = (done, type, loading) => {
  // 如果是打开的新增弹窗并且有新增子集的按钮
  if (type === 'add' && initOption.value.addSonBtn) {
    formData.value.parentId = 'root'
  }
  if (
    (type === 'add' || type === 'edit') &&
    initOption.value.column.some((item) => item.autoCode)
  ) {
    getOrDisabledCode(done, loading)
  } else {
    if (typeof props.beforeOpen === 'function') {
      props.beforeOpen(done, type, loading)
    } else {
      done()
    }
  }
}
// 弹窗关闭
const handleBeforeClose = (done, type) => {
  formData.value = {}
  if (props.beforeClose) {
    props.beforeClose(done, type)
  } else {
    done()
  }
}

const crudRef = ref()
// 表单数据
const formData = defineModel('formData', {
  type: Object,
  default: () => ({})
})
// 搜索数据
const searchData = defineModel('search', {
  type: Object,
  default: () => ({})
})
// 快速模糊搜索数据
const vagueSearchData = defineModel('vagueSearch', {
  type: Object,
  default: () => ({})
})
// 页码数据
const pageData = ref({
  total: 0,
  currentPage: 1,
  pageSize: 20,
  pageSizes: [10, 20, 50, 100, 200, 500],
  ...props.pageData,
})
// 状态映射
const statusMap = ref([])
// 表单数据
const tableData = ref([])
// 表单loading
const loading = ref(false)

// 处理分页大小变化
const handleChangeSize = (val) => {
  pageData.value.pageSize = val
  getList()
}

// 处理当前页码变化
const handleChangeCurrent = (val) => {
  pageData.value.currentPage = val
  getList()
}
// 获取列表
const getList = async (params = {}, done) => {
  const { pageSize, currentPage } = pageData.value
  let resParams = {
    // 如果是树表则默认不分页
    pageSize: props.option.isTableTree ? -1 : pageSize,
    pageNum: currentPage,
    ...props.defaultListParams,
    ...searchData.value,
    ...vagueSearchData.value,
    ...params,
    ...props.apiListParams,
  }
  apiGetList(
    props.beforeGetList ? props.beforeGetList(resParams) : resParams,
    loading
  )
    .then((resData) => {
      const { code, msg, data, total, dicData } = resData
      statusMap.value = dicData || []
      let idx = initOption.value.column.findIndex(item => item.statusTag)
      if (idx > -1) {
        initOption.value.column[idx]['dicData'] = dicData
      }
      if (code !== 200) {
        ElMessage.error(msg)
      } else {
        tableData.value =
          (props.afterGetList ? props.afterGetList(data) : data) || []
        pageData.value.total = total
      }
      // 表格勾选回显
      if (props.selectType !== 'none') {
        getSelectedIds()
      }
      props.afterGetListSuccess && props.afterGetListSuccess(err)
    })
    .catch((err) => {
      ElMessage.error(err.msg)
      props.afterGetListError && props.afterGetListError(err)
    })
    .finally((res) => {
      done && done()
    })
}

// 勾选的数据
const selectData = ref([])
// 单选多选时手动钩中
const getSelectedIds = () => {
  // 树结构处理
  const getTableTreeSelectData = (list) => {
    const selectedKey = props.selectedKey
    const ids = defaultSelectList.value.map((item) => item[selectedKey])
    list.forEach((item) => {
      if (ids.includes(item[selectedKey])) {
        selectedDataList.push(item)
      }
      if (
        props.option.isTableTree &&
        Array.isArray(item.children) &&
        item.children.length > 0
      ) {
        getTableTreeSelectData(item.children)
      }
    })
  }
  const selectedDataList = []
  getTableTreeSelectData(tableData.value)
  nextTick(() => {
    crudRef.value?.toggleSelection(selectedDataList, true)
  })
}

// 点击搜索按钮
const handleSearch = (params, done) => {
  for (const key in vagueSearchData.value) {
    // 确保属性属于目标对象自身，而不是原型链上的
    if (key != 'vagueText') {
      delete vagueSearchData.value[key];
    } else {
      //模糊查询字段清空
      vagueSearchData.value[key] = '';
    }
  }
  pageData.value.currentPage = 1
  getList(params, done)
}
// 重置搜索,为了提醒BasicCrudTableSelect组件
const handleReset = (params, done) => {
  emitter.emit('BasicCrudTableSelect_reset')
  for (const key in vagueSearchData.value) {
    // 确保属性属于目标对象自身，而不是原型链上的
    if (key != 'vagueText') {
      delete vagueSearchData.value[key];
    } else {
      //模糊查询字段清空
      vagueSearchData.value[key] = '';
    }
  }
  getList(params, done)
}

// 单个勾选
const handleSelect = (selection, row) => {
  const selectedKey = props.selectedKey
  const curRow = selection.find(
    (item) => item[selectedKey] === row[selectedKey]
  )
  if (curRow) {
    // 单选就直接赋值就行
    if (props.selectType === 'radio') {
      defaultSelectList.value = [row]
    } else if (props.selectType === 'multiple') {
      defaultSelectList.value.push(row)
    }
  } else {
    const idx = defaultSelectList.value.findIndex(
      (item) => item[selectedKey] === row[selectedKey]
    )
    if (idx !== -1) {
      defaultSelectList.value.splice(idx, 1)
    }
  }
  emits('select', selection, row)
}

// 处理选中项变化
const handleChangeSelect = (selection) => {
  if (props.selectType === 'radio' && selection.length > 1) {
    const preVal = selection.shift()
    crudRef.value.toggleRowSelection(preVal, false)
  }
  emits('selection-change', selection)
}

// 新增保存
const handleAddSave = async (row, done, loading) => {
  let endParams = { ...row, ...props.apiAddParams }
  let resParams = props.beforeAdd ? props.beforeAdd(endParams) : endParams
  apiAdd(resParams)
    .then(() => {
      ElMessage.success('新增成功！')
      props.afterAddSuccess && props.afterAddSuccess(row)
      done(row)
      getList()
    })
    .catch((err) => {
      props.afterAddError && props.afterAddError(err)
    })
    .finally((res) => {
      loading()
      props.afterAdd && props.afterAdd(res)
    })
}

// 新增子集
const handleAddSon = (row) => {
  crudRef.value.rowAdd()
  setTimeout(() => {
    formData.value.parentId = row.id
  }, 200)
}

// 编辑保存
const handleEditSave = async (row, index, done, loading) => {
  if (typeof attrs.onRowUpdate === 'function') {
    // 检测到外部传入了修改方法则直接调用外部不走内部
    return
  }
  let endParams = { ...row, ...props.apiEditParams }
  apiEdit(props.beforeEdit ? props.beforeEdit(endParams) : endParams)
    .then(() => {
      ElMessage.success('修改成功！')
      props.afterEditSuccess && props.afterEditSuccess(row)
      done(row)
      getList()
    })
    .catch((err) => {
      props.afterEditError && props.afterEditError(err)
    })
    .finally(() => {
      loading()
      props.afterEdit && props.afterEdit(res)
    })
}

// 删除
const handleDel = (row) => {
  ElMessageBox.confirm(props.delTip, '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
    beforeClose: async (action, instance, done) => {
      if (action === 'confirm') {
        instance.confirmButtonLoading = true
        console.log(row, 'row');
        apiDel(row.id)
          .then(() => {
            ElMessage.success('删除成功！')
            props.afterDelSuccess && props.afterDelSuccess(row)
            done()
            getList()
          })
          .catch((err) => {
            props.afterDelError && props.afterDelError(err)
          })
          .finally((res) => {
            instance.confirmButtonLoading = false
            props.afterDel && props.afterDel(res)
          })
      } else {
        done()
      }
    },
  })
}
// 切换启用停用状态
const handToggleStatus = async (row) => {
  let apiFun = null
  let msg = ''
  if (row.status === 'Disabled') {
    apiFun = apiEnable
    msg = '启用'
  } else {
    apiFun = apiDisable
    msg = '停用'
  }
  ElMessageBox.confirm(`确认${msg}该数据?`, '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
    beforeClose: async (action, instance, done) => {
      if (action === 'confirm') {
        instance.confirmButtonLoading = true
        apiFun(row)
          .then(() => {
            ElMessage.success(`${msg}成功！`)
            done()
            getList()
          })
          .finally(() => {
            instance.confirmButtonLoading = false
          })
      } else {
        done()
      }
    },
  })
}
// 状态切换
const handleChangeStatus = (row, status) => {
  row._statusChangeLoading = true
  apiChangeStatus({ id: row.id, status })
    .then(() => {
      ElMessage.success(`操作成功！`)
      getList()
    })
    .finally(() => {
      row._statusChangeLoading = false
    })
}


if (props.autoLoad) {
  getList()
}

watch(
  () => props.apiListParams,
  (newV) => {
    props.paramsChangeAutoRefresh && getList()
  },
  {
    deep: true,
  }
)

watch(
  () => formData.value,
  (newV) => {
    emits('formDataChange', newV)
  },
  { deep: true, immediate: true }
)
// 监听回显数据
watch(
  () => defaultSelectList.value,
  (newV) => {
    nextTick(() => {
      selectData.value = newV
      crudRef.value?.clearSelection()
      if (props.selectType !== 'none') {
        getSelectedIds()
      }
    })
  },
  { immediate: true, deep: true }
)
defineExpose({
  getList,
  selectData,
  formData,
  searchData,
  vagueSearchData,
  pageData,
  tableData,
  crudRef,
  handleChangeStatus,
  setCurrentRowHandler
})
</script>

<style lang="scss" scoped>
// 单选隐藏全选按钮
.radio_crud {
  ::v-deep {
    th.el-table-column--selection .cell {
      display: none !important;
    }

    td .cell .el-checkbox__inner {
      border-radius: 50% !important;
    }
  }
}
</style>
